# coding: utf-8
import re
from app.common.const import SftpConst
from app.common.logger import log


class PasswordChecker:

    @staticmethod
    def check_password_complexity(password, username=""):
        """检查密码是否符合所有规则"""
        if not PasswordChecker._check_length(password):
            return False
        if not PasswordChecker._check_categories(password):
            return False
        if not PasswordChecker._check_consecutive_chars(username, password, 3):
            return False
        if not PasswordChecker._check_repeated_chars(password):
            return False
        if not PasswordChecker._check_keyboard_patterns(password):
            return False
        if not PasswordChecker._check_sensitive_words(password):
            return False
        if not PasswordChecker._check_palindrome(password):
            return False
        return True

    @staticmethod
    def _check_length(password):
        """检查密码长度是否在12到64个字符之间"""
        if not (12 <= len(password) <= 64):
            log.error("Password check failed: Length must be between 12 and 64 characters.")
            return False
        return True

    @staticmethod
    def _check_categories(password):
        """检查密码是否包含至少三种字符类型（大写字母，小写字母，数字，特殊字符）"""
        categories = 0
        if re.search(r"[A-Z]", password):
            categories += 1
        if re.search(r"[a-z]", password):
            categories += 1
        if re.search(r"\d", password):
            categories += 1
        if re.search(r"[!@#\$%\^&\*\(\)_\+\-=\[\]\{\};:'\",<>\?/\\\|~`]", password):
            categories += 1
        if categories < 3:
            log.error(
                "Password check failed: Password must contain at least 3 character types (uppercase, lowercase, digits, special characters).")
            return False
        return True

    @staticmethod
    def _check_consecutive_chars(string, password, min_length):
        """检查密码是否包含来自用户名的连续字符，或用户名倒写的连续字符"""
        if string:
            # 检查用户名中的连续字符
            for i in range(len(string) - min_length + 1):
                if string[i:i + min_length].lower() in password.lower():
                    log.error(
                        f"Password check failed: Password contains {min_length} or more consecutive characters from the username.")
                    return False

            # 检查用户名倒写中的连续字符
            reversed_string = string[::-1]
            for i in range(len(reversed_string) - min_length + 1):
                if reversed_string[i:i + min_length].lower() in password.lower():
                    log.error(
                        f"Password check failed: Password contains {min_length} or more consecutive characters from the reversed username.")
                    return False
        return True

    @staticmethod
    def _check_repeated_chars(password):
        """检查密码是否包含4个及以上相同的连续字符"""
        if re.search(r"(.)\1{3,}", password):
            log.error("Password check failed: Password contains 4 or more repeated characters.")
            return False
        return True

    @staticmethod
    def _check_keyboard_patterns(password):
        """检查密码是否包含键盘上的连续字符"""
        keyboard_patterns = [
            "qwertyuiop", "asdfghjkl", "zxcvbnm", "1234567890",
            "!@#$%^&*()", "`-=[]\\;',./", "~!@#$%^&*()_+{}|:\"<>?"
        ]
        lower_password = password.lower()
        for pattern in keyboard_patterns:
            for i in range(len(pattern) - 3):
                if pattern[i:i + 4] in lower_password:
                    log.error("Password check failed: Password contains 4 or more consecutive keyboard characters.")
                    return False
        return True

    @staticmethod
    def _check_sensitive_words(password):
        """检查密码是否包含敏感词"""
        sensitive_words = [
            "root", "bin", "daemon", "adm", "lp", "sync", "shutdown", "halt",
            "mail", "operator", "games", "ftp", "nobody", "dbus", "tss", "polkitd",
            "libstoragemgmt", "unbound", "setroubleshoot", "chrony", "ntp",
            "sshd", "cloudmonitor", "rpcuser", "nfsnobody", "radvd", "dnsmasq", "saslauth",
            "qemu", "gluster", "dhcpd", "hwcdm"
        ]
        for word in sensitive_words:
            if word in password.lower():
                log.error(f"Password check failed: Password contains sensitive word '{word}'.")
                return False
        return True

    @staticmethod
    def _check_palindrome(password):
        """检查密码是否为回文字符串"""
        password = password.lower()
        if password == password[::-1]:
            log.error("Password check failed: Password is a palindrome.")
            return False
        return True


class PasswordPolicy:
    @staticmethod
    def update_password_policy():
        """
        更新Linux密码策略配置文件, 设置密码长度、历史记录、无效尝试次数和锁定时长
        """
        try:
            pwd_ruler_configs = SftpConst.PWD_RULER_SYSTEM_AUTH_LOCAL, SftpConst.PWD_RULER_PASSWORD_AUTH_LOCAL
            for pwd_ruler_config in pwd_ruler_configs:
                with open(pwd_ruler_config, "r+") as f:
                    config_content = f.read()
                    new_config = config_content

                    # 分别调用各个配置更新方法
                    new_config = PasswordPolicy._update_pwhistory(new_config)
                    new_config = PasswordPolicy._update_faillock_auth(new_config)
                    new_config = PasswordPolicy._update_faillock_account(new_config)

                    # 检查是否有变更
                    if new_config != config_content:
                        # 清空文件并写入新内容
                        f.seek(0)
                        f.truncate()
                        f.write(new_config)
                        log.info(f"Successfully modified password policy configuration file: {pwd_ruler_config}")
            return True
        except Exception as e:
            log.error(f"Failed to modify password policy configuration file: {e}")
            return False

    @staticmethod
    def _update_config(config, check_pattern, search_pattern, replacement, append_line):
        """
        通用配置更新方法
        """
        if not re.search(check_pattern, config):
            if re.search(search_pattern, config):
                config = re.sub(search_pattern, replacement, config)
                log.info(f"Updated configuration: {replacement}")
            else:
                config += f"\n{append_line}\n"
                log.info(f"Added configuration: {append_line}")
        return config

    @staticmethod
    def _update_pwhistory(config):
        """
        更新密码历史记录配置, 设置记忆最近4次密码
        """
        check_pattern = r"pam_pwhistory\.so\s+.*remember=4"
        search_pattern = r"pam_pwhistory\.so\s+.*remember=\d+"
        replacement = "pam_pwhistory.so remember=4 enforce_for_root"
        append_line = "password     required     pam_pwhistory.so remember=4 enforce_for_root"

        return PasswordPolicy._update_config(
            config,
            check_pattern,
            search_pattern,
            replacement,
            append_line
        )

    @staticmethod
    def _update_faillock_auth(config):
        """
        更新 pam_faillock.so 的 auth 配置，设置无效尝试次数和锁定时长。
        """
        check_pattern = r"pam_faillock\.so\s+deny=10\s+unlock_time=1800"
        search_pattern = r"^auth\s+required\s+pam_faillock\.so.*$"
        replacement = "auth     required     pam_faillock.so deny=10 unlock_time=1800"
        append_line = "auth     required     pam_faillock.so deny=10 unlock_time=1800"

        return PasswordPolicy._update_config(
            config,
            check_pattern,
            search_pattern,
            replacement,
            append_line
        )

    @staticmethod
    def _update_faillock_account(config):
        """
        更新 pam_faillock.so 的 account 配置, 在用户成功登录时, 重置失败次数计数器。同时确保在账户被锁定时, 适当地限制用户的访问
        """
        check_pattern = r"account\s+required\s+pam_faillock\.so"
        search_pattern = r"^account\s+required\s+pam_faillock\.so.*$"
        replacement = "account     required     pam_faillock.so"
        append_line = "account     required     pam_faillock.so"

        return PasswordPolicy._update_config(
            config,
            check_pattern,
            search_pattern,
            replacement,
            append_line
        )